package vg.modules.notepad.components;

import java.awt.BorderLayout;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;

import vg.modules.notepad.components.textComponent.ITextComponent;
import vg.services.plugin_manager.event_and_request.event.UIEventCloseAIF;
import vg.services.plugin_manager.event_and_request.event.UIEventOpenNewAIF;
import vg.services.plugin_manager.realization.PluginParameter;
import vg.services.user_interface_manager.additional_swing_components.SimpleTabWithCloseButton;

/**
 * This class realizes desktop for the notepad.
 * All methods of this class by need called from EDT only!!!
 * @author tzolotuhin
 */
public class Desktop {
	private static AtomicInteger counterId = new AtomicInteger(0);
	// Panels
	private final JPanel view;
	// Components
	private final JTabbedPane tabs;
	// Main data
	private final PluginParameter parameter;
	private final Map<Integer, String> comTabIdAndFullFileName; // composition full file names and tabs
	private final Map<Integer, String> comTabIdAndShortFileName; // composition short file names and tabs
	private final Map<Integer, ITextComponent> comTabIdAndTextComponent; // composition full file names and tabs
	private Map<Integer, Integer> comTabIdAndTab;
	// Additional data
	private final Map<String, ArrayList<Integer>> tabTitleStatistic; // statistic of titles
	// Mutex
	private final Object theMutexObject;
	/**
	 * Constructor.
	 * You need to create this component in EDT only!!!
	 */
	public Desktop(final PluginParameter param) {
		// init mutex
		this.theMutexObject = new Object();
		// init main data
		this.parameter = param;
		this.comTabIdAndShortFileName = new HashMap<Integer, String>();
		this.comTabIdAndFullFileName = new HashMap<Integer, String>();
		this.comTabIdAndTab = new HashMap<Integer, Integer>();
		this.comTabIdAndTextComponent = new HashMap<Integer, ITextComponent>();
		// init additional data
		this.tabTitleStatistic = new HashMap<String, ArrayList<Integer>>();
		// create components and panels
		this.view = new JPanel(new BorderLayout());
		this.tabs = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
		this.view.add(this.tabs, BorderLayout.CENTER);
		// ctrl-tab and ctrl-shift-tab
		KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {				
			public boolean dispatchKeyEvent(KeyEvent e) {
				if (e.getID() == KeyEvent.KEY_PRESSED &&  e.getKeyCode() == KeyEvent.VK_TAB && e.isControlDown()) {
					int tabcount = Desktop.this.tabs.getTabCount();
					if (tabcount > 0) {
						int sel = Desktop.this.tabs.getSelectedIndex();
						if (e.isShiftDown()) {
							sel = sel - 1;
							if (sel < 0)
								sel = tabcount - 1;
						} else {
							sel = sel + 1;
							if (sel >= tabcount)
								sel = 0;
						}
						Desktop.this.tabs.setSelectedIndex(sel);
					}
					return(true);
				}
				return(false);
			}
		});
	}
	public JComponent getView() {
		return(this.view);
	}
	/**
	 * This method adds new tab on the desktop.
	 */
	public void addTab(final String shortFileName, final String fullFileName, final ITextComponent textComponent) {
		if(SwingUtilities.isEventDispatchThread()) {
			synchronized (theMutexObject) {
				localAddTab(getNextId(), shortFileName, fullFileName, textComponent);
			}			
		} else {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					synchronized (theMutexObject) {
						localAddTab(getNextId(), shortFileName, fullFileName, textComponent);
					}
				}
			});
		}
	}
	/**
	 * This method adds new tab on the desktop.
	 */
	public void addTab(final int tabId, final String shortFileName, final String fullFileName, final ITextComponent textComponent) {
		if(SwingUtilities.isEventDispatchThread()) {
			synchronized (theMutexObject) {
				localAddTab(tabId, shortFileName, fullFileName, textComponent);
			}			
		} else {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					synchronized (theMutexObject) {
						localAddTab(tabId, shortFileName, fullFileName, textComponent);
					}
				}
			});
		}
	}
	/**
	 * This method returns current text component.
	 */
	public ITextComponent getCurrentTextComponent() {
		synchronized (theMutexObject) {			
			int index = this.tabs.getSelectedIndex();
			for(Integer buf : this.comTabIdAndTab.keySet()) {
				Integer value = this.comTabIdAndTab.get(buf);
				if((int)value == index) {
					return(this.comTabIdAndTextComponent.get(buf));
				}
			}
			return(null);
		}
	}
	/**
	 * This method returns current tab id, otherwise -1.
	 */
	public int getCurrentId() {
		synchronized (theMutexObject) {			
			int index = this.tabs.getSelectedIndex();
			for(Integer buf : this.comTabIdAndTab.keySet()) {
				Integer value = this.comTabIdAndTab.get(buf);
				if((int)value == index) {
					return(buf);
				}
			}
			return(-1);
		}
	}
	/**
	 * This method returns current full file name, otherwise null.
	 */
	public String getCurrentFullFileName() {
		synchronized (theMutexObject) {			
			int index = this.tabs.getSelectedIndex();
			for(Integer buf : this.comTabIdAndTab.keySet()) {
				Integer value = this.comTabIdAndTab.get(buf);
				if((int)value == index) {
					return(this.comTabIdAndFullFileName.get(buf));
				}
			}
			return(null);
		}
	}
	/**
	 * This method returns current short file name, otherwise null.
	 */
	public String getCurrentShortFileName() {
		synchronized (theMutexObject) {			
			int index = this.tabs.getSelectedIndex();
			for(Integer buf : this.comTabIdAndTab.keySet()) {
				Integer value = this.comTabIdAndTab.get(buf);
				if((int)value == index) {
					return(this.comTabIdAndShortFileName.get(buf));
				}
			}
			return(null);
		}
	}
	/**
	 * This method returns text component.
	 */
	public ITextComponent getComponent(final int tabId) {
		synchronized (theMutexObject) {
			return(this.comTabIdAndTextComponent.get(tabId));
		}
	}
	/**
	 * This method returns full file name.
	 */
	public String getFullFileNameAt(final int tabId) {
		synchronized (theMutexObject) {
			return(this.comTabIdAndFullFileName.get(tabId));
		}
	}
	/**
	 * This method updates user interface theme.
	 * 
	 */
	public void updateUITheme() {
		synchronized (theMutexObject) {
			Desktop.this.view.updateUI();
			for(ITextComponent buf : this.comTabIdAndTextComponent.values()) {
				buf.updateUITheme();
			}
		}
	}
	public void closeAllTab() {
		if(SwingUtilities.isEventDispatchThread()) {
			synchronized (theMutexObject) {
				while(this.tabs.getTabCount() != 0) {
					localCloseTab(0);
				}				
			}
		} else {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					synchronized (theMutexObject) {
						while(tabs.getTabCount() != 0) {
							localCloseTab(0);
						}
					}
				}
			});
		}
	}
	public void selectTabAt(final int tabId) {
		if(SwingUtilities.isEventDispatchThread()) {
			synchronized (theMutexObject) {
				if(this.comTabIdAndTab.containsKey(tabId)) {
					int index = this.comTabIdAndTab.get(tabId);
					if(index >= 0 && index < this.tabs.getTabCount()) {
						this.tabs.setSelectedIndex(index);
					}
				}
			}
		} else {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					synchronized (theMutexObject) {
						if(comTabIdAndTab.containsKey(tabId)) {
							int index = comTabIdAndTab.get(tabId);
							if(index >= 0 && index < tabs.getTabCount()) {
								tabs.setSelectedIndex(index);
							}
						}
					}
				}
			});
		}					
	}
	///////////////////////////////////////////////////////////////////////////
	// PRIVATE METHODS
	///////////////////////////////////////////////////////////////////////////
	/**
	 * This method adds new tab on the desktop.
	 */
	private int localAddTab(int tabId, String shortFileName, String fullFileName, final ITextComponent textComponent) {
		String localTitle = shortFileName;
		ArrayList<Integer>array = this.tabTitleStatistic.get(shortFileName);
		if(array == null) {
			array = new ArrayList<Integer>();
			array.add(0);
			this.tabTitleStatistic.put(shortFileName, array);
		} else {
			if(array.size() == 1 && array.get(0) == 0) {
				// renaming of existing tab
				array.remove(0);
				for(int i = 0 ; i < this.tabs.getTabCount(); i++) {
					if(this.tabs.getTitleAt(i).equals(shortFileName)) {
						this.tabs.setTitleAt(i, buildTabTitle(shortFileName, 1));
						break;
					}
				}
				array.add(1);
			}
			// find free index 
			int index = 1; // without zero
			for(int i = 0 ; i < array.size() ; i++) {
				if(!array.contains(index)) {
					break;
				}
				index++;
			}
			array.add(index);
			localTitle = buildTabTitle(localTitle, index);
		}
		// add information about new tab to data
		this.tabs.addTab(localTitle, textComponent.getView());
		this.comTabIdAndShortFileName.put(tabId, shortFileName);
		this.comTabIdAndFullFileName.put(tabId, fullFileName);	
		this.comTabIdAndTab.put(tabId, this.tabs.getTabCount() - 1);
		this.comTabIdAndTextComponent.put(tabId, textComponent);
		// add close button
		this.tabs.setTabComponentAt(this.tabs.getTabCount() - 1, new SimpleTabWithCloseButton(tabs, new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				int tabIndex = Desktop.this.tabs.indexOfTabComponent((SimpleTabWithCloseButton)e.getSource());
				localCloseTab(tabIndex);
			}
		}));
		// update ui
		this.view.updateUI();
		// create and send event (opening of new aif)
		if(this.parameter != null && this.parameter.userInterface != null) {
			UIEventOpenNewAIF event = new UIEventOpenNewAIF(tabId, fullFileName, shortFileName);
			this.parameter.userInterface.addEvent(event);
		}
		return(tabId);
	}
	/**
	 * This method closes tab.
	 * @param index - index of tab.
	 */
	private void localCloseTab(final int index) {
		if(index < 0) return;
		int tabId = -1;
		for(Integer buf : this.comTabIdAndTab.keySet()) {
			Integer value = this.comTabIdAndTab.get(buf);
			if((int)value == index) {
				String shortFileName = this.comTabIdAndShortFileName.get(buf);
				String tabTitle = this.tabs.getTitleAt(index);
				tabId = buf;
				// clear tab statistic
				ArrayList<Integer>array = this.tabTitleStatistic.get(shortFileName);
				if(array != null) {
					for(Integer bufInner : array) {
						String title = buildTabTitle(shortFileName, bufInner);
						if(title.equals(tabTitle)) {
							array.remove(bufInner);
							break;
						}
					}
					if(array.size() == 0) {
						this.tabTitleStatistic.remove(shortFileName);
					}
				}
				// clear compositions
				this.comTabIdAndFullFileName.remove(buf);
				this.comTabIdAndShortFileName.remove(buf);
				this.comTabIdAndTab.remove(buf);
				this.comTabIdAndTextComponent.remove(buf);
				// change next tab index
				Map<Integer, Integer>bufMap = new HashMap<Integer, Integer>();
				for(Integer bufInner : this.comTabIdAndTab.keySet()) {
					Integer val = this.comTabIdAndTab.get(bufInner);
					if((int)val > value) {
						bufMap.put(bufInner, val - 1);
					} else {
						bufMap.put(bufInner, val);
					}
				}
				this.comTabIdAndTab = bufMap;
				break;
			}
		}
		// update ui
		this.tabs.remove(index);
		this.view.updateUI();
		// create and send event (closing of new aif)
		if(tabId >= 0 && this.parameter != null && this.parameter.userInterface != null) {
			UIEventCloseAIF event = new UIEventCloseAIF(tabId);
			this.parameter.userInterface.addEvent(event);
		}
	}
	private String buildTabTitle(final String title, final int number) {
		if(number <= 0) return(title);
		return(title + ":" + number);
	}
	/**
	 * This method returns next id for tab.
	 */
	private int getNextId() {
		int a = counterId.incrementAndGet();
		return(a);
	}
}
